<template>
  <div id="canvas-relation">
    <canvas id="relation" width="4000" height="4000"></canvas>
  </div>
</template>

<script>
import { formatNumber } from "@/utils/util";

export default {
  name: "relation",
  props: {
    option: {
      type: Array,
      default: () => [],
    },
    type: {
      type: String,
      default: null,
    },
  },
  data() {
    return {
      leftData: [],
      rightData: [],
      width: null,
      height: null,
      arcLength: null, //弧总长度
      itemArcLength: null, //相邻两个节点之间的弧长
      nums: 9, //默认左右展示节点个数
      rad: 108, //默认左右弧圆心角
      arcTotal: null,
      centerStyle: {
        radius: 60, //半径
        startAngle: 0, //起始角度
        endAngle: 2 * Math.PI, //结束角度
        borderColor: "#FFBB73",
        shadowBlur: 20, //阴影
        shadowColor: "#FA8C16", //阴影颜色
        backgroundColor: "#FA8C16", //背景色
        font: "18px sans-serif", //字体样式
        color: "#FFFFFF",
      }, //中心圆样式配置
      arcStyle: {
        radius: 240, //弧默认半径
        startAngleLeft: 0.5 * Math.PI, //左边起始角度
        endAngleLeft: 1.5 * Math.PI, //左边结束角度
        startAngleRight: 1.5 * Math.PI, //右边起始角度
        endAngleRight: 0.5 * Math.PI, //右边结束角度
        strokeStyle: "#55a8ff", //弧颜色
        startBorderColorOpacity: "05", //弧起始点透明度
        endBorderColorOpacity: "80", //弧结束点透明度
      }, //弧样式配置
      nodeItemStyle: {
        radius: 12, //半径
        color: "#555555", //节点字体颜色
        font: "12px sans-serif", //节点字体样式
        backgroundColor: "#cddeff", //节点背景色
        borderColor: "#55a8ff", //节点边框颜色
      }, //节点样式配置
    };
  },
  watch: {
    option: {
      handler: function (val) {
        this.initData();
        this.render();
      },
      deep: true,
    },
  },
  methods: {
    /**
     * 初始化数据
     */
    initData() {
      this.rightData = [];
      this.leftData = [];
      if (this.option.length) {
        this.option.forEach((item, index) => {
          if (index % 2 === 0) {
            this.rightData.push(item);
          } else {
            this.leftData.push(item);
          }
        });
      }
    },
    /**
     * 渲染canvas
     */
    render() {
      let canvas = document.getElementById("relation");
      if (!canvas.getContext) {
        // console.log("不支持getContext");
        return;
      }
      let radius = this.getRadius();
      // 动态计算画布的宽高
      canvas.width = radius * 2 + 1000;
      canvas.height = radius * 2 + 1000;

      this.width = canvas.offsetWidth;
      this.height = canvas.height;
      let ctx = canvas.getContext("2d");
      ctx.clearRect(0, 0, this.width, this.height);
      //绘制中心圆形图
      this.renderCenter(ctx);

      //渲染两侧弧
      this.renderArc(ctx, radius);

      //渲染左右节点
      this.renderNode(ctx, radius, this.rightData, "right");
      this.renderNode(ctx, radius, this.leftData, "left");
    },
    /**
     * 绘制中心圆
     * @param ctx
     */
    renderCenter(ctx) {
      let { width, height } = this.getContentWidth(ctx, this.type);
      //设置覆盖样式
      ctx.globalCompositeOperation = "destination-over";
      ctx.beginPath();
      ctx.font = this.centerStyle.font;
      ctx.fillStyle = this.centerStyle.color;
      ctx.fillText(
        this.type,
        this.width / 2 - width / 1.5,
        this.height / 2 + height / 2
      );

      ctx.beginPath();
      ctx.arc(
        this.width / 2,
        this.height / 2,
        this.centerStyle.radius,
        this.centerStyle.startAngle,
        this.centerStyle.endAngle
      );
      ctx.shadowBlur = this.centerStyle.shadowBlur;
      ctx.shadowColor = this.centerStyle.shadowColor;
      ctx.fillStyle = this.centerStyle.backgroundColor;

      ctx.closePath();
      ctx.fill();
    },
    /**
     * 绘制弧
     * @param ctx
     * @param radius 半径
     */
    renderArc(ctx, radius) {
      let x = this.width / 2,
        y = this.height / 2,
        {
          strokeStyle: color,
          startBorderColorOpacity: startOpacity,
          endBorderColorOpacity: endOpacity,
        } = this.arcStyle;
      //绘制左侧弧
      ctx.beginPath();
      ctx.restore();
      ctx.arc(
        x,
        y,
        radius,
        this.arcStyle.startAngleLeft,
        this.arcStyle.endAngleLeft
      );
      let grdL = ctx.createLinearGradient(x, y, x - radius, y);
      grdL.addColorStop(0, `${color + startOpacity}`);
      grdL.addColorStop(1, `${color + endOpacity}`);
      ctx.shadowColor = "transparent";
      ctx.strokeStyle = grdL;
      ctx.stroke();
      //绘制右侧弧
      ctx.beginPath();
      ctx.restore();
      ctx.arc(
        x,
        y,
        radius,
        this.arcStyle.startAngleRight,
        this.arcStyle.endAngleRight
      );
      let grdR = ctx.createLinearGradient(x, y, x + radius, y);
      grdR.addColorStop(0, `${color + startOpacity}`);
      grdR.addColorStop(1, `${color + endOpacity}`);
      ctx.shadowColor = "transparent";
      ctx.strokeStyle = grdR;
      ctx.stroke();
    },
    /**
     * 绘制左右弧上的每个节点
     * 获取弧上每个点坐标公式：中心点x/y坐标 + Math.cos(Math.PI * 2 / 360 * 圆心角) * 半径
     * @param ctx
     * @param r 半径
     * @param nodeData 数据
     * @param direction
     */
    renderNode(ctx, r, nodeData, direction) {
      //计算每个节点对应的圆心角
      let arcLength =
          this.option.length / 2 > this.nums ? this.arcTotal : this.arcLength,
        angle =
          arcLength /
          (nodeData.length > 1 ? nodeData.length - 1 : nodeData.length) /
          r /
          (Math.PI / 180);

      for (let i = 0; i < nodeData.length; i++) {
        //计算初始点的角度（从弧的顶部开始计算）
        let startAngle = 180 - (180 - this.rad) / 2;
        let x,
          y,
          itemAngle = startAngle - i * (angle || 0);
        //计算每个角度对应的x,y坐标
        if (direction === "right") {
          x = this.width / 2 + Math.sin(((2 * Math.PI) / 360) * itemAngle) * r;
          y = this.height / 2 + Math.cos(((2 * Math.PI) / 360) * itemAngle) * r;
        } else {
          x = this.width / 2 - Math.sin(((2 * Math.PI) / 360) * itemAngle) * r;
          y = this.height / 2 + Math.cos(((2 * Math.PI) / 360) * itemAngle) * r;
        }
        //连接弧上节点和中心点
        this.connectPoint(ctx, { x, y });
        //绘制每个节点
        let name =
          nodeData[i].name + `（数量：${formatNumber(nodeData[i].num)}）`;
        this.drawNodeItem(ctx, { x, y }, r, name, direction);
      }
    },
    /**
     * 绘制节点与中心点的连接线
     * @param ctx
     * @param axis 坐标
     */
    connectPoint(ctx, axis) {
      let { x, y } = axis,
        centerX = this.width / 2,
        centerY = this.height / 2;
      ctx.beginPath();
      //设置覆盖样式
      ctx.globalCompositeOperation = "destination-over";
      ctx.moveTo(centerX, centerY);
      ctx.lineTo(x, y);
      let grd = ctx.createRadialGradient(
        centerX,
        centerY,
        0,
        centerX,
        centerY,
        240
      );
      grd.addColorStop(0, "#ffffff");
      grd.addColorStop(1, `${this.nodeItemStyle.borderColor}60`);
      ctx.lineWidth = 1;
      ctx.strokeStyle = grd;
      ctx.stroke();
    },
    /**
     * 绘制弧上的每个节点样式
     * @param ctx
     * @param axis 坐标
     * @param radius 半径
     * @param itemLabel 文字
     * @param direction 方向
     */
    drawNodeItem(ctx, axis, radius, itemLabel, direction) {
      let { x, y } = axis,
        { width, height } = this.getContentWidth(ctx, itemLabel);
      //计算文本离中心点的距离
      let contentWidth = width + this.nodeItemStyle.radius * 2;
      //开始绘制
      //绘制节点圆角矩形
      this.drawNodeItemRectangle(ctx, x, y, contentWidth, direction);
      //绘制节点左侧圆形
      this.drawNodeItemCircular(ctx, x, y, contentWidth, direction);
      //节点文本填充
      this.fillLabel(ctx, x, y, width, height, itemLabel, direction);
    },

    //绘制节点圆角矩形
    drawNodeItemRectangle(ctx, x, y, contentWidth, direction) {
      let r = this.nodeItemStyle.radius;
      ctx.restore();
      ctx.beginPath();
      if (direction === "right") {
        ctx.moveTo(x, y - r);
        ctx.lineTo(x + contentWidth + r, y - r);
        ctx.arc(x + contentWidth + r, y, r, 1.5 * Math.PI, 0.5 * Math.PI);
        ctx.lineTo(x, y + r);
      } else {
        ctx.moveTo(x, y + r);
        ctx.lineTo(x - contentWidth - r, y + r);
        ctx.arc(x - contentWidth - r, y, r, 0.5 * Math.PI, 1.5 * Math.PI);
        ctx.lineTo(x, y - r);
      }
      ctx.closePath();
      ctx.lineWidth = 1;
      //设置图层覆盖样式
      ctx.globalCompositeOperation = "source-over";
      ctx.shadowColor = "transparent";
      //边框颜色
      ctx.strokeStyle = this.nodeItemStyle.borderColor;
      ctx.stroke();
      //填充色
      ctx.fillStyle = this.nodeItemStyle.backgroundColor;
      ctx.fill();
    },
    //绘制节点左侧圆形
    drawNodeItemCircular(ctx, x, y, contentWidth, direction) {
      let r = this.nodeItemStyle.radius;
      ctx.beginPath();
      ctx.arc(x, y, r + 1, 0, 2 * Math.PI);
      //填充色
      ctx.fillStyle = this.nodeItemStyle.borderColor;
      ctx.closePath();
      ctx.fill();
    },
    //节点文本填充
    fillLabel(ctx, x, y, labelWidth, labelHeight, label, direction) {
      let r = this.nodeItemStyle.radius;
      if (!label) return;
      let labelOffsetX =
        direction === "right" ? x + 2 * r : x - labelWidth - 2 * r;
      ctx.beginPath();
      ctx.font = this.nodeItemStyle.font;
      ctx.fillStyle = this.nodeItemStyle.color;
      ctx.fillText(label, labelOffsetX, y + labelHeight / 2);
      ctx.closePath();
    },
    //获取文本字体宽高
    getContentWidth(ctx, text) {
      // 设置字体样式
      ctx.font = this.nodeItemStyle.font;
      let dimension = ctx.measureText(text);
      // console.log(dimension);
      return {
        width: dimension.width,
        height:
          dimension.fontBoundingBoxAscent - dimension.fontBoundingBoxDescent ||
          9,
      };
    },
    /**
     * 计算半径长度
     * 计算公式：半径 = 弧长 / ( 圆心角度数 * Math.PI / 180 )
     * @returns {number}
     */
    getRadius() {
      let radius = this.arcStyle.radius;
      //左右两侧数据最大值
      let maxLength = (this.option.length / 2).toFixed(0);
      //计算默认弧总长度
      this.arcLength = radius * ((this.rad * Math.PI) / 180);
      //计算相邻两个节点的弧长
      if (this.option.length) {
        let itemNums =
          this.option.length / 2 > this.nums ? this.nums : maxLength;
        this.itemArcLength = this.arcLength / (itemNums - 1);
        //计算实际总弧长
        this.arcTotal =
          this.itemArcLength *
          (maxLength % 2 === 1 ? maxLength - 1 : maxLength);
        // console.log('默认总弧长', this.arcLength)
        // console.log('实际总弧长', this.arcTotal)
        radius =
          this.option.length / 2 > 9 && this.arcTotal > this.arcLength
            ? this.arcTotal / ((this.rad * Math.PI) / 180)
            : radius;
      }
      return radius;
    },
  },
  mounted() {},
};
</script>

<style scoped lang="scss">
#canvas-relation {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
</style>
